endpoint 是一组 Pod 的集合的抽象,是 Kubernetes 里内置的资源类型,可以通过 Service 的 label 进行筛选得到。endpoint 往往伴随 service 诞生,对于定义 ExternalName 的 Service 或 clusterIP 直接定义为 None 的 Service(Headless Service) 来说,endpoint 也可以是不存在的。 endpoint 可以用来筛选出同 service 关联的所有 pod,将流量导入到 service 后,经过负载均衡的处理发送到这些 pod 上。
也可以单独定义 Endpoint 和 Service. 如果我们需要将 Kubernetes 集群之外的外部服务定义为 Kubernetes 内部的一个服务,可以分别定义 Service 和 Endpoints,Service 不需要定义标签选择器,定义一个与 Service 同名的 Endpoints,就可以与之关联。Endpoints 中的 subsets 中可以定义需要连接的外部服务器的 IP 和端口。
endpoint-controller 的基本逻辑是通过 informer 获取 Service,再根据 labels selector 筛选出相应的 pod,接着创建或更新 endpoints 对象,endpoints 其实就是一组 pod 的集合,只不过其中包括了 Node 的信息,而它的 name 属性即为 service 的 name。
初始化
这部分代码在 cmd/kube-controller-manager/app/core.go
中,跟其他的 controller 类似,通过 goroutine 创建 Controller,调用 Run
方法
1 | func startEndpointController(ctx ControllerContext) (http.Handler, bool, error) { |
可以看到 endpoint 初始化时,会注册三个 informer, 分别是 podInformer, serviceInformer, endpointsInformer, 在 NewEndpointController
方法中,会对 service, pod 资源进行 watch 操作,注册对应的 add, update, delete 等操作,这里就不给出代码了。举例来说,如果监听到了 add pod 的操作,就调用 addPod
方法。
1 | func (e *EndpointController) getPodServiceMemberships(pod *v1.Pod) (sets.String, error) { |
在 addPod
中,首先实例化一个 pod 对象,找到它属于哪一个 service, 然后将 service 的集合以 namespace/name 为 key 加入到队列中。
启动
Run
方法
1 | func (e *EndpointController) Run(workers int, stopCh <-chan struct{}) { |
通过 WaitForCacheSync
等待 pod,service,endpoint 的缓存同步完之后,根据配置启动相应数量(由 kube-controller-manager
启动参数中的 --concurrent-endpoint-syncs
决定)的 worker 进行处理,我们接着看 worker 方法。
1 | func (e *EndpointController) worker() { |
很简单的逻辑,从队列中取出 key,根据这个 key 可以获取 namespace,service name, 调用 syncService
方法进行处理。基本上 syncService
就是 endpoint-controller 的主体逻辑。
主体逻辑
1 | func (e *EndpointController) syncService(key string) error { |
首先通过 namespace,name 获取 service,如果 service 不存在,说明 service 已经被删除了,删除对应的 endpoint,这里有个没能解决的 bug 是,如果 service 删除了,但是这时 endpoint 刚好挂了,那么对应的 endpoint 就没法删除。Run
方法中调用的 checkLeftoverEndpoints
方法在 endpoint-controller 的启动阶段会做一次清理工作,它会拉取所有的 endpoint 的资源对象,遍历一遍,如果发现没有对应的 service, 把 endpoint 的 name 当做 key 传入队列,这时进入循环后会发现这个 endpoint 不能获取对应的 service name, 就会被删除。
接着通过 namespace,Selector 获取对应的 Pod 的集合,遍历这些 Pod,一一构建 endpoint 的 Subset,其中包括 Pod 的 IP 地址,Hostname,所在的 Node name 等信息。遍历结束后,通过 Service 的 Namespace 和 Name 获取当前的 Endpoint,与构建的新的 endpoint 进行比较,决定创建还是更新 endpoint 对象,通过 client 进行相应的调用。对于没有 Selector 的 Service,不需要创建相应的 endpoint。
简单来说,使用 Kubernetes 的时候我们不需要刻意关注 endpoint 资源对象,只有创建 service 的时候使用了 selector, 就可以自动创建 endpoint. 如果单独创建一个不绑定 service 的 endpoint, 它会被 Kubernetes 自动清理的。